package momo.app.status;

import java.text.SimpleDateFormat;
import java.util.Collections;
import java.util.Date;
import java.util.Iterator;
import java.util.Random;
import java.util.TreeSet;

import momo.app.comms.CommsManager;
import momo.app.config.AppConfig;
import momo.app.structure.Message;
import momo.app.structure.Peer;
import momo.app.structure.PeerLink;
import momo.app.structure.PeerList;
import momo.app.util.FileOutput;
import momo.multitree.simulation.SimulatedAnnealing;
import momo.multitree.structure.Node;
import momo.multitree.structure.Tree;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;


public class RootElection extends Thread
{
	public static final int VOTING_WAITING_TIME = 10 * 1000;
	
	private Log log = LogFactory.getLog(RootElection.class);
	private Log logStructure = LogFactory.getLog("MoMoStructureLog");
	
	private boolean electionStarts;
	private boolean votingStarts;
	
	private Random rand;
	private int myCandidateId;
	private TreeSet<Integer> candidates;
	private int votingWaitingTime;
	private String electionType;
	
	public RootElection() {
		electionStarts = false;
		votingStarts = false;
		rand = new Random();
		candidates = new TreeSet<Integer>(Collections.reverseOrder());
		votingWaitingTime = VOTING_WAITING_TIME;
		electionType = Message.GENERAL_ELECTION;
	}
	
	public void run() {
		if ( log.isDebugEnabled() )
			log.debug("Event: Root Election event has just started");
		if ( logStructure.isDebugEnabled() )
			logStructure.debug("Event: Root Election event has just started");
		
		try {
			if ( log.isDebugEnabled() )
				log.debug("Voting Starts NOW !!!");
			if ( logStructure.isDebugEnabled() )
				logStructure.debug("Voting Starts NOW !!!");
			
			votingStarts = true;
			broadcastCandidateId();
			sleep(votingWaitingTime);
			
			if ( log.isDebugEnabled() )
				log.debug("Voting has just END !!!");
			if ( logStructure.isDebugEnabled() )
				logStructure.debug("Voting has just END !!!");
			
			votingStarts = false;
			
			electionStarts = true;
			
			if ( log.isDebugEnabled() )
				log.debug("Election Now. Who will be the winner?");
			if ( logStructure.isDebugEnabled() )
				logStructure.debug("Election Now. Who will be the winner?");
			
			if ( getSelectedCandidateId() == myCandidateId ) {
				if ( log.isDebugEnabled() )
					log.debug("I won!! I will be doing the re-structuring");
				if ( logStructure.isDebugEnabled() )
					logStructure.debug("I won!! I will be doing the re-structuring");
				
				restructure();
			}else {
				if ( log.isDebugEnabled() )
					log.debug("I have lost....");
				if ( logStructure.isDebugEnabled() )
					logStructure.debug("I have lost....");
			}
			electionStarts = false;
			
			if ( log.isDebugEnabled() )
				log.debug("Election ends...");
			if ( logStructure.isDebugEnabled() )
				logStructure.debug("Election ends...");
			
		}catch (InterruptedException ie) {
		}catch(Exception e) {
			if ( log.isErrorEnabled() )
				log.error("Exception", e);
			if ( logStructure.isDebugEnabled() )
				logStructure.error("Exception", e);
		}
		
		votingStarts = false;
		electionStarts = false;
		candidates.clear();
		
		if ( log.isDebugEnabled() )
			log.debug("Event: Root Election event has just end");
		if ( logStructure.isDebugEnabled() )
			logStructure.debug("Event: Root Election event has just end");
		
	}
	
	synchronized private void broadcastCandidateId() {
		generateMyCandidateId();
		
		if ( log.isDebugEnabled() )
			log.debug("My candidate id: " + getMyCandidateId());
		
		StatusManager statusManager = StatusManager.getInstance();
		
		statusManager.broadcast(
				new Message(Message.BROADCAST,
						AppConfig.getInstance().getId(),
						electionType,
						AppConfig.getInstance().getMe().toString()+"|"+getMyCandidateId()));
	}
	
	synchronized public void restructure()
	{
		if ( log.isDebugEnabled() )
			log.debug("Restructuring in progress");
		if ( logStructure.isDebugEnabled() )
			logStructure.debug("Restructuring in progress");

		StatusManager statusManager = StatusManager.getInstance();
		
		if ( !statusManager.getPeerList().isConnectedGraph() )
		{
			if ( log.isDebugEnabled() )
				log.debug("Unconnected graph. Trying to update all links to make connected graph");
			if ( logStructure.isDebugEnabled() )
				logStructure.debug("Unconnected graph. Trying to update all links to make connected graph");
			
			LatencyPoolThread.updateLatency();
		}
		
		StringBuffer buffer = new StringBuffer();
		
		int maxIteration = 100;
		int maxTrial = 100;
		double temperature = 0d;
		double factor = 0.98d;
		
		statusManager.removeUnconnectedPeers();
//		statusManager.displayPeer();
//		statusManager.displayPeerLink();
		
		if ( statusManager.getPeers().size() == 1 )
		{
			log.debug("I m the only guy left in the network");
			logStructure.debug("I m the only guy left in the network");
//			FileOutput.getInstance().logToFile("structure.log", "I m the only guy left in the network");
			statusManager.setMeAsRoot();
			CommsManager.getInstance().stopElectionTimeout();
			return;
		}
		
		statusManager.calWorkload("Root Election");
		statusManager.rescaleWorkloadRate();
		SimulatedAnnealing sim = new SimulatedAnnealing(statusManager.getPeerList(), maxIteration, maxTrial, temperature, factor);
		Tree bestTree = sim.optimizeTree();
		
		//output to verify for planetlab
		outputVisualForPlanetLab(bestTree);
		
//		test method
//		Tree bestTree = this.getNewTree(statusManager.getPeerList());
		
		Peer root = (Peer)bestTree.getRoot();
		double score = sim.score(bestTree);
		buffer.append(score+"|"+root.toString()+"|");
		
		Iterator<Node> iter = bestTree.getNodes().iterator();
		while (iter.hasNext())
		{
			Peer child = (Peer)iter.next();
			if ( !child.getId().equals(root.getId()) )
			{
				Peer parent = (Peer)bestTree.getParent(child) ;
				buffer.append(child.toString()+"|"+parent.toString()+"|");
			}
		}
		
		Message newStruct = new Message(Message.BROADCAST,
										AppConfig.getInstance().getId(),
										Message.NEW_STRUCTURE,
										buffer.toString());
		
		if ( log.isDebugEnabled() )
			log.debug(newStruct.getMsg());
		if ( logStructure.isDebugEnabled() )
			logStructure.debug(newStruct.getMsg());
		
		statusManager.broadcast(newStruct);
		CommsManager.getInstance().restructure(newStruct);
		
	}
	
	private void outputVisualForPlanetLab(Tree tree) {
		try {
			Date now = new Date();
			SimpleDateFormat format = new SimpleDateFormat("dd-MM-yyyy, HH:mm:ss:SSSS");
			String filename = format.format(now); 
			tree.outputVisual( AppConfig.getInstance().getId() + "_" + filename );
		}catch (Exception e) {
			log.error(e);
		}
	}
		
	synchronized public int getMyCandidateId()
	{
		return myCandidateId;
	}
	
	/**
	 * Generates a candidate Id and stores it into the voting list. 
	 * @return generated candidate id
	 */
	synchronized private void generateMyCandidateId()
	{
		if ( this.isAlive() )
		{
			if ( this.hasVotingStarts() )
			{
				myCandidateId = rand.nextInt();
				storeCandidateId(myCandidateId);
			}
		}
	}
	
	/**
	 * Stores all candidates Id into the voting list
	 * @param candidateId
	 * @return
	 */
	synchronized public boolean storeCandidateId(int candidateId)
	{
		if ( this.isAlive() )
			if ( this.hasVotingStarts() )
				return candidates.add(candidateId);
		return false;
	}
	
	/**
	 * The selected candidate id will be the largest integer in the voting list
	 * @return selected candidate id
	 */
	synchronized public int getSelectedCandidateId()
	{
		return candidates.first();
	}

	synchronized public String getElectionType() {
		return electionType;
	}

	synchronized public void setElectionType(String electionType) {
		this.electionType = electionType;
	}
	
	public boolean hasElectionStarts()
	{
		return electionStarts;
	}
	
	public boolean hasVotingStarts()
	{
		return votingStarts;
	}
	
	public void stopEvent()
	{
		if ( this.isAlive() )
			this.interrupt();
	}
}//end of class RootElection_2
